• Jump To … +
    main.js separate.js single.js web-apg-api.js main.js web-conv-api.js ast.js csv.js dangling-else.js display.js flags.js float.js limits.js main.js multiline-mode.js recursive.js replace.js rules.js split.js testonly.js trace.js udt.js unicode.js web-email.js word-boundaries.js main.js phone-number.js web-main.js web-phone-number.js main.js phone-number.js setup.js translate.js xml.js branch-fail-grammar.js main.js parent-mode-grammar.js setup.js universal-mode-grammar.js colors-app.js colors-callbacks.js colors.js main.js more-app.js more-setup.js more.js ast-callbacks.js bad-input.js basic.js ini-file.js main.js parser-callbacks.js setup.js trace.js anbncn.js and.js c-comment.js compound.js main.js nested.js not.js setup.js boundaries-grammar.js boundaries.js comment-grammar.js comment.js main.js negative-grammar.js negative.js positive-grammar.js positive.js setup.js main.js odata-grammar.js run.js setup.js area-code.js lookaround.js main.js phone-number.js setup.js simple.js all-operators.js default.js fancy-number.js limited-lines.js main.js select-operators.js select-rules.js setup.js main.js minimal.js parent-u.js parent.js phone-number.js setup.js stats.js trace.js universal-u.js universal.js callbacks.js grammar.js main.js parser.js writeHtml.js LICENSE.md README.md index.md
  • main.js

  • §
    /* eslint-disable func-names */
    /* eslint-disable new-cap */
    /*  *************************************************************************************
     *   copyright: Copyright (c) 2021 Lowell D. Thomas, all rights reserved
     *     license: BSD-2-Clause (https://opensource.org/licenses/BSD-2-Clause)
     *   ********************************************************************************* */
  • §

    This is a demonstration of the bare minimum needed to set up a parser and parse a given input string.

    module.exports = function main(args) {
      /* display the program arguments */
      console.log();
      console.log('Demonstrate the bare minimum to set up a parser and parse an input string.');
      console.log();
      console.log('apg-lib args');
      console.dir(args);
    
      /* the help screen */
      let desc = '';
      desc += 'The "apg-lib" example is a simple demonstration using of the JavaScript APG library, apg-lib.\n';
      desc += 'It is also a demonstration of the basics of creating and using a JavaScript APG parser.\n';
      desc += 'A simple phone number grammar is used to parse an input phone number string.\n';
      desc += 'It defines and uses callback functions and generates statistics and a trace of the parse tree.\n';
      desc += 'Examine or run a debugger on this module, "apg-examples/src/apg-lib/main.js", to study the example.\n';
      desc += '\n';
      desc += 'The ABNF grammar is in the file phone-number.bnf. The grammar object, phone-number.js\n';
      desc += 'can be regenerated with:\n';
      desc += 'npm run apg -- -i src/apg-lib/phone-number.bnf -o src/apg-lib/phone-number\n';
      desc += '\n';
      desc += 'Note that for web page usage, the generated grammar function needs to be renamed. e.g.\n';
      desc += 'npm run apg -- -i src/apg-lib/phone-number.bnf -o src/apg-lib/web-phone-number -n grammarObject\n';
      desc += '\n';
      let help = 'Usage: npm run simple [-- arg]\n';
      help += '       or\n';
      help += '       ./src/apg-lib/main.sh [arg]\n';
      help += '  arg: help - (or no arg) to display this help screen.\n';
      help += '       go   - (or any other argument other than "help") to parse a phone number.\n';
      if (!args[0]) {
        /* display the description and help screen and exit */
        console.log(desc);
        console.log(help);
        return;
      }
      if (args[0] === 'help') {
        /* display the help screen and exit */
        console.log(help);
        return;
      }
    
      const nodeUtil = require('node:util');
      const fs = require('node:fs');
      const inspectOptions = {
        showHidden: true,
        depth: null,
      };
      const doStats = true;
      const doTrace = true;
      try {
  • §

    Get the apg library. The variable apgLib has references to all of the library objects and object contructors.

        const { apgLib } = require('apg-js');
  • §

    Create a parser object. This gets further definition later with the grammar object, the stats and trace objects and the callback function references.

        const parser = new apgLib.parser();
  • §

    The grammar object defines the SABNF grammar the parser will use to parse an input string. phone-number.js is the output of apg for the SABNF grammar defined by the ./phone-number.bnf file.

        const obj = require('./phone-number');
        const grammar = new obj();
  • §

    These identifiers are used by the callback functions to identify the state of the parser at the time the callback function was called.

        const id = apgLib.ids;
  • §

    This creates a stats object and attaches it to the parser. When attached, the parser will initialize the object and collect parsing statistics with it for each parse tree node it visits.

        if (doStats) {
          parser.stats = new apgLib.stats();
        }
  • §

    This creates a trace object and attaches it to the parser. When attached, the parser will initialize the object and collect tracing records for each parse tree node it visits (see the ./src/trace example for details on filtering the records).

        if (doTrace) {
          parser.trace = new apgLib.trace();
        }
  • §

    The utility library in apg-lib has a number of utility functions that are often helpful, even essential for handling string, character codes, HTML display of results and other things.

        const { utils } = apgLib;
  • §

    The next four variables define the parser callback functions for the rule name phrases we are interested in. Callback functions are optional and can be defined for all or none of the rule names defined by the SABNF grammar. Normally, these will be defined in a module of their own to keep the flow of the application clean, but are included here to keep things simple. The callback function arguments are:

    • result - communicates the parsing results to and from the callback functions (see the parser.parse() function in apg-lib for a complete description). Here only result.state and result.phraseLength are of interest.
    • chars - the array of character codes for the input string being parsed.
    • phraseIndex - index to the first character in chars of the phrase the parser is attempting to match
    • data - an optional user data object passed to parser.parse() by the user. For callback function use only. The parser never modifies or uses this in any way.
        const phoneNumber = function (result, chars, phraseIndex, data) {
          switch (result.state) {
            case id.ACTIVE:
              if (Array.isArray(data) === false) {
                throw new Error("parser's user data must be an array");
              }
              data.length = 0;
              break;
            /* the following cases not used in this example */
            case id.EMPTY:
              break;
            case id.MATCH:
              break;
            case id.NOMATCH:
              break;
            default:
              throw new Error('unrecognized state');
          }
        };
    
        const areaCode = function (result, chars, phraseIndex, data) {
          if (result.state === id.MATCH) {
            /* capture the area code */
            data['area-code'] = utils.charsToString(chars, phraseIndex, result.phraseLength);
          }
        };
        const office = function (result, chars, phraseIndex, data) {
          if (result.state === id.MATCH) {
            /* capture the 3-digit central office or exchange number */
            data.office = utils.charsToString(chars, phraseIndex, result.phraseLength);
          }
        };
        const subscriber = function (result, chars, phraseIndex, data) {
          if (result.state === id.MATCH) {
            /* capture the 4-digit subscriber number */
            data.subscriber = utils.charsToString(chars, phraseIndex, result.phraseLength);
          }
        };
  • §

    Define which rules the parser will call callback functions for. (NOTE: the generated grammar object, phone-number.js in this case, will have a pre-defined callbacks array for all rule names.)

        parser.callbacks['phone-number'] = phoneNumber;
        parser.callbacks['area-code'] = areaCode;
        parser.callbacks.office = office;
        parser.callbacks.subscriber = subscriber;
    
        /* use a hard-coded input string for this example */
        const inputString = '(555)234-5678';
    
        /* convert string to character codes */
        const inputCharacterCodes = utils.stringToChars(inputString);
    
        /* set the parser's "start rule" */
        const startRule = 'phone-number';
    
        /* the callback function's *data* */
        const phoneParts = [];
  • §

    This is the call that will finally parse the input string.

        const result = parser.parse(grammar, startRule, inputCharacterCodes, phoneParts);
    
        /* display parser results */
        console.log();
        console.log("the parser's results");
        console.dir(result, inspectOptions);
        if (result.success === false) {
          throw new Error(`input string: '${inputString}' : parse failed`);
        }
    
        /* display phone number parts, captured as matched phrases in the callback functions */
        console.log();
        console.log(`phone number: ${inputString}`);
        console.log(`   area-code: ${phoneParts['area-code']}`);
        console.log(`      office: ${phoneParts.office}`);
        console.log(`  subscriber: ${phoneParts.subscriber}`);
        console.log();
        const dir = `${process.cwd()}/output`;
        if (doStats) {
  • §

    This section will demonstrate all of the options for the display of the parsing statistics. Finally, all options will be displayed on a single web page. See the page html/simple-stats.html for the results.

          let html = '';
          html += parser.stats.toHtml('ops', 'ops-only stats');
          html += parser.stats.toHtml('index', 'rules ordered by index');
          html += parser.stats.toHtml('alpha', 'rules ordered alphabetically');
          html += parser.stats.toHtml('hits', 'rules ordered by hit count');
          const name = `${dir}/simple-stats.html`;
          try {
            fs.mkdirSync(dir);
          } catch (e) {
            if (e.code !== 'EEXIST') {
              throw new Error(`fs.mkdir failed: ${e.message}`);
            }
          }
          html = utils.htmlToPage(html, 'simple-stats');
          fs.writeFileSync(name, html);
          console.log(`view "${name}" in any browser to display parsing statistics`);
        }
    
        if (doTrace) {
  • §

    This section will demonstrate the display of the parser’s trace. See the page html/simple-trace.html for the results.

          const html = parser.trace.toHtmlPage('ascii', 'good phone number, default trace');
          const name = `${dir}/simple-trace.html`;
          try {
            fs.mkdirSync(dir);
          } catch (e) {
            if (e.code !== 'EEXIST') {
              throw new Error(`fs.mkdir failed: ${e.message}`);
            }
          }
          fs.writeFileSync(name, html);
          console.log(`view "${name}" in any browser to display parser's trace`);
        }
      } catch (e) {
        let msg = '\nEXCEPTION THROWN: \n';
        if (e instanceof Error) {
          msg += `${e.name}: ${e.message}`;
        } else if (typeof e === 'string') {
          msg += e;
        } else {
          msg += nodeUtil.inspect(e, inspectOptions);
        }
        console.log(msg);
        throw e;
      }
    };